import java.util.*;
import java.math.*;
import Jama.*;

import org.lsmp.djep.groupJep.groups.*; //for Zn

public class HIM{

    int n = Utilities.n_prime; // no. of players
    int fieldSize = Utilities.F;

    Random randGen;
    ArrayList<BigInteger> as, bs;
    double[][] HIMatrix;
    static Matrix M;

    Zn ring = new Zn(Utilities.F_big);

    HIM(){
        randGen = new Random(0);
        as = new ArrayList<BigInteger>();
        bs = new ArrayList<BigInteger>();
        HIMatrix = new double[n][n];
    }

    public void constructHIM(){

        //First, make sure that the a's and b's contain distinct elements
        HashSet<BigInteger> h = new HashSet<BigInteger>();
        while(h.size() < 2*n){
            h.add(BigInteger.valueOf(randGen.nextInt(fieldSize)));
        }
        as = new ArrayList<BigInteger>();
        bs = new ArrayList<BigInteger>();

        Iterator<BigInteger> it = h.iterator();
        while(it.hasNext()){
            if(as.size() < n){
                as.add(it.next());
            }
            else{
                bs.add(it.next());
            }
        }

        for(int i = 0 ; i<n ; i++){
            for(int j = 0 ; j<n ; j++){
                BigInteger lambda = BigInteger.valueOf(1);
                for(int k = 0 ; k<n ; k++){
                    if(k != j){                        
                        //cannot be division by zero, since elem. are distinct. 
                        BigInteger top = (BigInteger)ring.sub(bs.get(i), as.get(k));//(bs.get(i)).doubleValue()-(as.get(k)).doubleValue();
                        BigInteger bot = (BigInteger)ring.sub(as.get(j), as.get(k));//(as.get(j)).doubleValue()-(as.get(k)).doubleValue();
                        lambda = lambda.multiply((BigInteger)ring.div(top, bot));//lambda *= top/bot;
                    }
                }
                HIMatrix[i][j] = lambda.mod(Utilities.F_big).doubleValue();
            }
        }
        M = new Matrix(HIMatrix);
        M.print(3,3);        
    }

    public static Matrix getHIM(){
        return M;
    }

    public void printHIM(){
        M.print(3,3);
    }

    /**
     * @return The computation [r_i]_d = M([s_i]_d)
     */    
    public BigInteger[] applyShares(BigInteger[] shares){
        //Shave M down to the size it should have:
        int[] ri = new int[shares.length];
        int[] ci = new int[shares.length];
        for(int i = 0; i< shares.length; i++){
            ri[i] = i;
            ci[i] = i;
        }
        Matrix subMatrix = M.getMatrix(ri, ci);
        //convert biginteger[] into a double array for matrix use
        double[][] ds = new double[shares.length][1];
        for(int i = 0; i< shares.length; i++){
            ds[i][0] = shares[i].doubleValue();
        }
        
        Matrix res = Matrix.constructWithCopy(ds);
        Matrix tmp = subMatrix.times(res);
        double[][] r_i = tmp.getArrayCopy();
        BigInteger[] result = new BigInteger[Utilities.n_prime];
        for(int i = 0; i < Utilities.n_prime; i++){
            //TODO: We only operate with numbers from Z_p, so we can
            //transfer double directly into BigInteger without loosing
            //precision... - no need for the ring this time around.
            result[i] = BigInteger.valueOf(new Double(r_i[i][0]).longValue()).mod(Utilities.F_big);
        }
        return result;
    }
}